//
// Created by martin on 2/15/22.
//

#include "Acceptor.h"
#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/Socket.h"
#include "muduo/net/SocketsOps.h"
#include "muduo/net/InetAddress.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

using namespace muduo;
using namespace muduo::net;

/**
 * Acceptor 构造函数
 * @param loop 所属EventLoop对象, 包含loop循环
 * @param listenAddr 本地要监听的地址&端口
 * @param reuseport 对应socket选项SO_REUSEPORT
 */
Acceptor::Acceptor(EventLoop *loop, const InetAddress &listenAddr, bool reuseport)
: loop_(loop),
  acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())), // 创建一个非阻塞监听sock fd
  acceptChannel_(loop, acceptSocket_.fd()),
  listening_(false),
  idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC)) // 申请空闲fd
{
    assert(idleFd_ >= 0);
    acceptSocket_.setReuseAddr(true);
    acceptSocket_.setReusePort(reuseport);
    acceptSocket_.bindAddress(listenAddr);
    acceptChannel_.setReadCallback(
            std::bind(&Acceptor::handleRead, this));
}

Acceptor::~Acceptor()
{
    acceptChannel_.disableAll(); // disable all event of the channel
    acceptChannel_.remove(); // remove the channel from poller
    ::close(idleFd_);
}

/**
 * 监听本地sock fd, 使能监听Channel读事件
 */
void Acceptor::listen()
{
    loop_->assertInLoopThread();
    listening_ = true;
    acceptSocket_.listen();         // 使能监听本地sock fd(ip, port)
    acceptChannel_.enableReading(); // 使能监听通道读事件
}

/**
 * 处理读Channel事件, accept连接
 * @note 先accept, 然后将相关资源通过回调交由上一层TcpServer进行处理(管理)
 */
void Acceptor::handleRead()
{
    loop_->assertInLoopThread();
    InetAddress peerAddr;
    // FIXME loop until no more
    int connfd = acceptSocket_.accept(&peerAddr); // 获取连接fd及对端ip地址
    if (connfd >= 0)
    {
        if (newConnectionCallback_)
        { // 创建新连接回调
            newConnectionCallback_(connfd, peerAddr);
        }
        else
        {
            sockets::close(connfd);
        }
    }
    else
    { // 错误
        LOG_SYSERR << "in Acceptor::handleRead";
        /*
         * Read the section named "The special problem of
         * accept()ing when you can't" in libev's doc.
         * By Marc Lehmann, author of libev.
         *
         * The per-process limit of open file descriptors has been reached.
         */
        if (errno == EMFILE)
        { // 文件描述符资源耗尽错误
            ::close(idleFd_);
            idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
            ::close(idleFd_);
            // reopen /dev/null, it dose not matter whether it succeeds or fails.
            idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
        }
    }
}
